//
//  BlueUtil.m
//  这是封装CoreBlueTooth的工具类的实现
//
//  Created by Willy Liu on 2020/6/22.
//  Copyright © 2020 gao. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <CoreBluetooth/CoreBluetooth.h>
#import "BlueUtil.h"//一般自己写的头文件用".h"调用类库才用<.h>,因为<*.h>表示优先中系统目录中查找
#import "callback/ScanCallback.h"
#import "callback/ConnectCallback.h"
#import "callback/WriteCallback.h"
#import "callback/ReceiverCallback.h"
@interface BlueToothUtil ()<CBCentralManagerDelegate, CBPeripheralDelegate>
@property(strong,nonatomic)CBPeripheral * peripheral;//连接的蓝牙设备对象
@property(assign,nonatomic) BOOL isScanTarget;//判断扫描是否扫描到了目标设备
@property(assign,nonatomic) BOOL isInitiativeToDisConnect;//判断扫描是否扫描到了目标设备
@property(strong,nonatomic)NSString * M_BLE_UUID;//蓝牙设备的uuid,用于判断是否是目标设备
@property(assign,nonatomic)BOOL isInitable;//判断蓝牙功能是否初始化完成
@property(strong,nonatomic)CBCentralManager *centralManager;//本地蓝牙管理
@property(strong,nonatomic)NSMutableDictionary<NSString *,NSMutableDictionary<NSString *, CBCharacteristic *> *> * serviceMap;//服务和特征值的map
@property(strong,nonatomic)NSMutableSet<NSString *> * alreadyExistsNotify;//已经开启了notify的特征值，防止多次开启
@property(strong,nonatomic)id scanCallback;//扫描设备的回调，objective-c不像java一样可以直接使用接口指针来实现多态，使用protocol只能间接使用id这个通用类型来指向实现了protocol协议的类型
@property(strong,nonatomic)id connectCallback;//连接设备之后的回调
@property(strong,nonatomic)id writeCallback;//向设备写入数据之后的回调
@property(strong,nonatomic)id receiverCallback;//接收到设备的数据之后的回调
@property(assign,nonatomic)int keepScanTime;//持续扫描的时间
@end

@implementation BlueToothUtil

//判断是否扫描到了目标设备
- (BOOL)isScanTarget{
    return _isScanTarget;
}

//设置各种uuid
- (void)setUuid:(NSString *) blueUuid{
    _M_BLE_UUID=blueUuid;
}

//设置扫描之后的回调
-(void)setScanCallback:(id) scanCallback{
    _scanCallback=scanCallback;
}

//设置连接之后的回调
-(void)setConnectCallback:(id) connectCallback{
    _connectCallback=connectCallback;
}

//设置发送数据之后的回调
-(void)setWriteCallback:(id) writeCallback{
    _writeCallback=writeCallback;
}

//设置接收到数据之后的回调
-(void)setReceiverCallback:(id) receiverCallback{
    _receiverCallback=receiverCallback;
}

//设置扫描时间
-(void)setKeepScanTime:(int)keepScanTime{
    _keepScanTime=keepScanTime;
}

//判断是否是自己主动去断开连接的
-(BOOL)isInitiativeDisconnect{
    return self.isInitiativeToDisConnect;
}

//连接外围设备
-(void)connectPeripheral{
    if(_isInitable==YES){
        //蓝牙初始化之后才能扫描
        /*
         -- 两个参数为Nil表示默认扫描所有可见蓝牙设备。
         -- 注意：第一个参数是用来扫描有指定服务的外设。然后有些外设的服务是相同的，比如都有FFF5服务，那么都会发现；而有些外设的服务是不可见的，就会扫描不到设备,第二个参数代表是否运行重复扫描相同的设备
         -- 成功扫描到外设后调用didDiscoverPeripheral
         */
        self.isInitiativeToDisConnect=NO;
        NSLog(_centralManager==nil?@"yes":@"no");
        [_centralManager scanForPeripheralsWithServices:nil options:nil];//开始扫描
        [self performSelector:@selector(myStopScan) withObject:nil afterDelay:_keepScanTime];//扫描keepScanTims秒之后停止扫描
    }else//如果没有初始化就去初始化蓝牙
        _centralManager =[[CBCentralManager alloc]initWithDelegate:self queue:nil];
}

//与当前设备断开连接
-(void) disConnectPeripheral{
    if(_peripheral!=nil){//如果当前有连接
        [_centralManager cancelPeripheralConnection:self.peripheral];//断开连接
        self.isInitiativeToDisConnect=YES;//标志此次是自己主动去断开连接的
    }
}

//停止正在连接的行为
-(BOOL) stopConnecting{
    if(!_isScanTarget){//如果当前还没有扫描到目标设备
        [self myStopScan];//停止扫描
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(myStopScan) object:nil];//取消定时停止扫描的任务
        return YES;//返回yes代表停止成功
    }
    return NO;//返回no代表停止失败，说明此时已经连接上设备了
}

// 发送数据
-(void)sendDataToBLE:(NSData *)data serviceUuid:(NSString *) serviceUuid writeUuid:(NSString *)writeUuid notifyUuid:(NSString *)notifyUuid isEnable:(BOOL) isEnable{
    if(isEnable&&![_alreadyExistsNotify containsObject:notifyUuid]){//如果要开启这个notify通道并且这个notify通道是关闭的话
        CBCharacteristic * notifyCharacteristic =[self getCharacteristicFromMap:serviceUuid characteristicUuid:notifyUuid];//获取notify通道特征值
        [self.peripheral setNotifyValue:YES forCharacteristic:notifyCharacteristic];//开启notify的通知
        [_alreadyExistsNotify addObject:notifyUuid];//添加进已经开启了notify的通道set
    }else if(!isEnable&&[_alreadyExistsNotify containsObject:notifyUuid]){//如果要关闭这个notify并且这个notify通道是开启的话
        CBCharacteristic * notifyCharacteristic =[self getCharacteristicFromMap:serviceUuid characteristicUuid:notifyUuid];//获取notify通道特征值
        [_peripheral setNotifyValue:NO forCharacteristic:notifyCharacteristic];//关闭notify的通知
        [_alreadyExistsNotify removeObject:notifyUuid];//从set中移除
    }
    CBCharacteristic * writeCharacteristic =[self getCharacteristicFromMap:serviceUuid characteristicUuid:writeUuid];
    if(nil != writeCharacteristic){//如果有这个特征值的话
            // data: 数据data
            // characteristic: 发送给哪个特征
            // type:     CBCharacteristicWriteWithResponse,  CBCharacteristicWriteWithoutResponse,
            //这里要跟硬件确认好，写入的特征是否有允许写入，允许用withResponse 不允许只能强行写入，用withoutResponse
        [_peripheral writeValue:data forCharacteristic:writeCharacteristic type:CBCharacteristicWriteWithResponse];
    }
}

//判断某个字符串是不是空
- (BOOL)checkIsNull:(NSString *) string{
    if ([string isEqual:@"NULL"] || [string isKindOfClass:[NSNull class]] || [string isEqual:[NSNull null]] || [string isEqual:NULL] || [[string class] isSubclassOfClass:[NSNull class]] || string == nil || string == NULL || [string isKindOfClass:[NSNull class]] || [[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length]==0 || [string isEqualToString:@"<null>"] || [string isEqualToString:@"(null)"])
        return NO;
    return YES;
}

//获取serviceMap里的某个特征值
-(CBCharacteristic *) getCharacteristicFromMap:(NSString *) serviceUuid characteristicUuid:(NSString *)characteristicUuid{
    CBCharacteristic * result=nil;
    NSMutableDictionary<NSString *, CBCharacteristic *> * charactMap=[_serviceMap objectForKey:serviceUuid];//获取对应服务下的特征值Map
    if(charactMap!=nil)//如果有这个服务的话
        result=[charactMap objectForKey:characteristicUuid];//获取目标特征值
    return result;
}

//初始化设备后，会走代理判断蓝牙状态
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    switch (central.state) {
        case CBManagerStateUnknown:
            NSLog(@"CBCentralManagerStateUnknown");
            break;
        case CBManagerStateResetting:
            NSLog(@"CBCentralManagerStateResetting");
            break;
        case CBManagerStateUnsupported:
            NSLog(@"CBCentralManagerStateUnsupported");
            break;
        case CBManagerStateUnauthorized:
            NSLog(@"CBCentralManagerStateUnauthorized");
            break;
        case CBManagerStatePoweredOff://蓝牙没有打开
            NSLog(@"蓝牙未打开");
            _isInitable=NO;
            break;
        case CBManagerStatePoweredOn: //蓝牙打开了
            NSLog(@"蓝牙已经打开");
            _isInitable=YES;//将初始化状态设置为已完成
            [self connectPeripheral];//初始化成功之后就去连接设备
            break;
        default:
            break;
    }
}
    
//发现设备列表后判断
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
    //NSLog(@"\n设备名称：%@",peripheral.name);
    if([self checkIsNull:peripheral.name]){//有时候搜索到的设备名称和信息都是null,即nil
            NSLog(@"\n设备名称：%@",peripheral.name);
            NSArray *uuidArray  =[advertisementData objectForKey:@"kCBAdvDataServiceUUIDs"];//整体是NSArrayM(数组类型)，里面的对象是CBUUID(CORE BLUETOOTH封装的对象)
            NSString *uuid=[uuidArray[0] UUIDString];
            NSLog(@"\n获取到的UUID：%@",uuid);
        if(_scanCallback!=nil&&[_scanCallback conformsToProtocol:@protocol(ScanCallback)])
            [_scanCallback onScanning:self];//调用传入调扫描到一个设备之后的回调函数
        if([_M_BLE_UUID isEqualToString:uuid]){//如果是我们的目标设备就直接连接
            _isScanTarget=YES;//设置找到了目标设备
            [self myStopScan];//停止扫描
            [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(myStopScan) object:nil];//取消定时停止扫描的任务
            if(_peripheral==nil){//如果当前没有连接才让他连接
                _peripheral=peripheral;//如果这里不把peripheral强引用起来，后面会丢失，导致无法连接设备
                [_centralManager connectPeripheral:peripheral options:nil];//连接设备
            }
        }
    }
}

//外设连接成功的回调
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    NSLog(@"设备连接成功:%@", peripheral.name);
    // 设置代理
    [_peripheral setDelegate:self];//外设的服务、特征、描述等方法是CBPeripheralDelegate的内容，所以要先设置代理peripheral.delegate = self
    //MARK: 6.1 外设发现服务,参数表示你关心的服务的UUID，比如我关心的是"FFE0",参数就可以为@[[CBUUID UUIDWithString:@"FFE0"]].那么didDiscoverServices方法回调内容就只有这两个UUID的服务，不会有其他多余的内容，提高效率。nil表示扫描所有服务
    _serviceMap=[[NSMutableDictionary alloc] init];//初始化服务和特征值的map
    _alreadyExistsNotify=[[NSMutableSet alloc] init];//初始化alreadySet
    if(_connectCallback!=nil&&[_connectCallback conformsToProtocol:@protocol(ConnectCallback)])
        [_connectCallback connectSuccess:self];//调用连接成功之后的回调函数
    [_peripheral discoverServices:nil];//发现服务
}

// 外设连接失败的回调
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    NSLog(@"设备连接失败:%@", peripheral.name);
    _peripheral=nil;//消除连接对象
    if(_connectCallback!=nil&&[_connectCallback conformsToProtocol:@protocol(ConnectCallback)])
        [_connectCallback connectFailed:self];//调用连接失败之后的回调函数
}

// 丢失连接的回掉
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    NSLog(@"设备丢失连接:%@", peripheral.name);
    _peripheral=nil;//消除连接对象
    [_alreadyExistsNotify removeAllObjects];//清空再把引用设置为nil,以防万一
    _alreadyExistsNotify=nil;
    [_serviceMap removeAllObjects];
    _serviceMap=nil;
    if(_connectCallback!=nil&&[_connectCallback conformsToProtocol:@protocol(ConnectCallback)])
        [_connectCallback disConnect:self];//调用断开链接之后的回调函数
}
    
// 发现外设的服务后调用的方法
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    // 是否获取失败
    if (error) {
        NSLog(@"设备获取服务失败:%@", peripheral.name);
        return;
    }
    for (CBService *service in peripheral.services) {
        NSLog(@"设备的服务(%@),UUID(%@),count(%lu)",service,service.UUID,peripheral.services.count);
        if([_serviceMap objectForKey:[service.UUID UUIDString]]==nil)//如果发现过此服务就不用再添加进去了
            [_serviceMap setValue:[[NSMutableDictionary alloc] init] forKey:[service.UUID UUIDString]];//将获取到的服务加到map中，并初始化特征值的map
        //发现特征
        [peripheral discoverCharacteristics:nil forService:service];
    }
}
    
//从服务中发现外设特征的时候的回调方法
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    for (CBCharacteristic *characteristic in service.characteristics) {
        NSLog(@"获取到服务的特征值：%@",[characteristic.UUID UUIDString]);
        NSMutableDictionary<NSString *, CBCharacteristic *> * charactMap=[_serviceMap objectForKey:[service.UUID UUIDString]];//获取对应服务下的特征值Map
            [charactMap setValue:characteristic forKey:[characteristic.UUID UUIDString]];//将特征值加入到map中
    }
}


// 发送数据完成的回调
-(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    if (error) {
        NSLog(@"写入数据失败:(%@)\n error:%@",characteristic,error.userInfo);
        if(_writeCallback!=nil&&[_writeCallback conformsToProtocol:@protocol(WriteCallback)])
            [_writeCallback writeFailed:[characteristic.UUID UUIDString]];//调用写入数据失败之后的回调函数
        // 这里用withResponse如果报错："Writing is not permitted."说明设备不允许写入，这个时候要用 WithoutResponse
        // 使用 WithoutResponse的时候，不走这个代理。
        return;
    }
    if(_writeCallback!=nil&&[_writeCallback conformsToProtocol:@protocol(WriteCallback)])
        [_writeCallback writeSuccess:[characteristic.UUID UUIDString]];//调用写入数据成功之后的回调函数
    [peripheral readValueForCharacteristic:characteristic];
}


//notify传回数据时的回调
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    if(_receiverCallback!=nil&&[_receiverCallback conformsToProtocol:@protocol(ReceiverCallback)]&&characteristic.value!=nil)
        [_receiverCallback onReceiver:[characteristic.UUID UUIDString] data:characteristic.value controller:self];//调用接收到notify数据之后的回调函数
}

//停止扫描
-(void)myStopScan{
    [_centralManager stopScan];// 停止扫描
    if(_scanCallback!=nil&&[_scanCallback conformsToProtocol:@protocol(ScanCallback)])
        [_scanCallback scanFinish:self];//调用传入的扫描结束之后的回调函数
}
@end
